/*!
\copyright  Copyright (c) 2020 - 2022 Qualcomm Technologies International, Ltd.
            All Rights Reserved.
            Qualcomm Technologies International, Ltd. Confidential and Proprietary.
\file
\brief     Implementation of the voice source interface for LE Voice sources.
*/

#if defined(INCLUDE_LE_AUDIO_UNICAST)

#include "le_unicast_voice_source.h"
#include "le_unicast_telephony_control.h"
#include "le_unicast_manager_private.h"
#include "kymera_adaptation_voice_protected.h"
#include "voice_sources.h"
#include "micp_server.h"

#include <logging.h>
#include <panic.h>
#include <stdlib.h>

static bool leUnicastVoiceSource_GetAudioConnectParameters(voice_source_t source, source_defined_params_t *source_params);
static void leUnicastVoiceSource_FreeAudioConnectParameters(voice_source_t source, source_defined_params_t *source_params);
static bool leUnicastVoiceSource_GetAudioDisconnectParameters(voice_source_t source, source_defined_params_t *source_params);
static void leUnicastVoiceSource_FreeAudioDisconnectParameters(voice_source_t source, source_defined_params_t *source_params);
static bool leUnicastVoiceSource_IsVoiceChannelAvailable(voice_source_t source);
static bool leUnicastVoiceSource_IsAudioRouted(voice_source_t source);

static const voice_source_audio_interface_t voice_source_audio_interface =
{
    .GetConnectParameters = leUnicastVoiceSource_GetAudioConnectParameters,
    .ReleaseConnectParameters = leUnicastVoiceSource_FreeAudioConnectParameters,
    .GetDisconnectParameters = leUnicastVoiceSource_GetAudioDisconnectParameters,
    .ReleaseDisconnectParameters = leUnicastVoiceSource_FreeAudioDisconnectParameters,
    .IsAudioRouted = leUnicastVoiceSource_IsAudioRouted,
    .IsVoiceChannelAvailable = leUnicastVoiceSource_IsVoiceChannelAvailable,
    .SetState = NULL,
    .GetState = NULL
};

static uint16 leUnicastVoiceSource_GetFrameLength(le_um_ase_t *ase)
{
    uint8 frame_blocks_per_sdu = LeUnicastManager_GetCodecFrameBlocksPerSdu(ase->codec_info);

    return LeUnicastManager_GetFramelength(ase->qos_info->maximumSduSize, 
                                                            frame_blocks_per_sdu, LeUnicastManager_GetAudioLocation(ase->codec_info));
}

static void leUnicastVoiceSource_GetStreamConfig(le_um_ase_t *ase, le_um_ase_t *ase_r, stream_config *stream)
{
    stream->codec_frame_blocks_per_sdu = LeUnicastManager_GetCodecFrameBlocksPerSdu(ase->codec_info);
    stream->frame_length = leUnicastVoiceSource_GetFrameLength(ase);
    stream->presentation_delay = ase->qos_info->presentationDelay;
    stream->stream_type = leUnicastManager_DetermineStreamType(ase, ase_r);
}

/* \brief Extract audio parameters from Codec and Qos Information */
static bool leUnicastVoiceSource_ExtractAudioParameters(le_voice_connect_parameters_t *conn_param)
{
    bool populate_success = FALSE;
    le_um_ase_t *sink_ase;
    le_um_ase_t *source_ase;
    multidevice_side_t side = Multidevice_GetSide();

    UNICAST_MANAGER_LOG("leUnicastVoiceSource_ExtractAudioParameters :");

    LeUnicastManager_GetAsesForGivenSide(side, &sink_ase, &source_ase);

    /* Fill in the Audio Parameters */
    if (sink_ase != NULL && sink_ase->state == le_um_ase_state_streaming)
    {
        le_um_ase_t *sink_ase_r = NULL;
        le_um_cis_t *voice_cis_info = sink_ase->cis_data;

        /* To prevent non-zero, garbage values during initialization in handle */
        conn_param->le_voice_config.source_iso_handle_right = LE_AUDIO_INVALID_ISO_HANDLE;

        if (Multidevice_IsDeviceStereo())
        {
            sink_ase_r = LeUnicastManager_GetRightSinkAse();
            if (LeUnicastManager_IsAseActive(sink_ase_r))
            {
                sink_ase_r->state = le_um_ase_state_routed;
                conn_param->le_voice_config.source_iso_handle_right = sink_ase_r->cis_data->cis_handle;
            }
            else
            {
                sink_ase_r = NULL;
            }
        }

        conn_param->volume = VoiceSources_CalculateOutputVolume(voice_source_le_audio_unicast);
        conn_param->le_voice_config.source_iso_handle = voice_cis_info->cis_handle;
        conn_param->le_voice_config.sample_rate = LeUnicastManager_GetSampleRate(sink_ase->codec_info);
        conn_param->le_voice_config.frame_duration = LeUnicastManager_GetFrameDuration(sink_ase);
        conn_param->le_voice_config.codec_type = KYMERA_LE_AUDIO_CODEC_LC3; /* @TODO this has to be derived by application from the ASE configuration data. */
        conn_param->le_voice_config.codec_version = sink_ase->codec_version;

        leUnicastVoiceSource_GetStreamConfig(sink_ase, sink_ase_r, &conn_param->le_voice_config.speaker_stream);
        sink_ase->state = le_um_ase_state_routed;

        populate_success = TRUE;
        UNICAST_MANAGER_LOG("Speaker path iso_handle left : %d, iso_handle_right : %d, sink_iso_handle : %d, Volume %d",
                            conn_param->le_voice_config.source_iso_handle,
                            conn_param->le_voice_config.source_iso_handle_right,
                            conn_param->le_voice_config.sink_iso_handle,
                            conn_param->volume.value);

        UNICAST_MANAGER_LOG(" sample_rate : %d, frame_duration %d, codec_type: %d, codec_version: %d",
                            conn_param->le_voice_config.sample_rate,
                            conn_param->le_voice_config.frame_duration,
                            conn_param->le_voice_config.codec_type,
                            conn_param->le_voice_config.codec_version);
    }

    if (source_ase != NULL)
    {
        le_um_cis_t *mic_cis_info = source_ase->cis_data;

        conn_param->le_voice_config.sink_iso_handle = mic_cis_info->cis_handle;
        conn_param->le_voice_config.mic_mute_state = (uint8)MicpServer_GetMicState();
        leUnicastVoiceSource_GetStreamConfig(source_ase, NULL, &conn_param->le_voice_config.mic_stream);

        /* Don't move source ASE state to streaming as it needs to be done only after
         * getting ReadyToReceive unicast client
         */
        source_ase->state = le_um_ase_state_routed;
        UNICAST_MANAGER_LOG("Microphone path iso_handle: %d", conn_param->le_voice_config.sink_iso_handle);
    }

    return  populate_success;
}

static bool leUnicastVoiceSource_GetAudioConnectParameters(voice_source_t source, source_defined_params_t *source_params)
{
    bool populate_success = FALSE;

    UNICAST_MANAGER_LOG("leUnicastVoiceSource_GetAudioConnectParameters");

    if (source == voice_source_le_audio_unicast)
    {
        le_voice_connect_parameters_t *conn_param =
            (le_voice_connect_parameters_t *) PanicUnlessMalloc(sizeof(le_voice_connect_parameters_t));

        if (leUnicastVoiceSource_ExtractAudioParameters(conn_param))
        {
            source_params->data = (void *)conn_param;
            source_params->data_length = sizeof(le_voice_connect_parameters_t);
            populate_success = TRUE;
        }
        else
        {
            /* Free the allocated voice connect parameter */
            pfree(conn_param);
        }
    }

    UNICAST_MANAGER_LOG("leUnicastVoiceSource_GetAudioConnectParameters success %d", populate_success);
    return populate_success;
}

static void leUnicastVoiceSource_FreeAudioConnectParameters(voice_source_t source,
                                                            source_defined_params_t *source_params)
{
    if (source == voice_source_le_audio_unicast)
    {
        PanicNull(source_params);
        PanicFalse(source_params->data_length == sizeof(le_voice_connect_parameters_t));

        pfree(source_params->data);
        source_params->data = (void *)NULL;
        source_params->data_length = 0;
    }
}

static bool leUnicastVoiceSource_GetAudioDisconnectParameters(voice_source_t source,
                                                              source_defined_params_t *source_params)
{
    UNUSED(source);

    PanicNull(source_params);
    source_params->data = (void *)NULL;
    source_params->data_length = 0;

    return TRUE;
}

static void leUnicastVoiceSource_FreeAudioDisconnectParameters(voice_source_t source,
                                                               source_defined_params_t *source_params)
{
    UNUSED(source);

    PanicNull(source_params);
    source_params->data = (void *)NULL;
    source_params->data_length = 0;;
}

static bool leUnicastVoiceSource_IsAseReady(const le_um_ase_t *ase)
{
    bool result = FALSE;
    le_um_cis_t *cis_info = ase->cis_data;

    if (cis_info != NULL && 
        LeUnicastManager_IsContextTypeConversational(ase->audio_context) &&
        LeUnicastManager_IsCisEstablished(cis_info->state) &&
        LeUnicastManager_IsAseActive(ase))
    {
        result = TRUE;
    }

    UNICAST_MANAGER_LOG("leUnicastVoiceSource_IsAseReady result %d", result);
    return result;
}

static bool leUnicastVoiceSource_IsVoiceChannelAvailable(voice_source_t source)
{
    bool audio_available = FALSE;

    UNICAST_MANAGER_LOG("leUnicastVoiceSource_IsVoiceChannelAvailable");

    if (source == voice_source_le_audio_unicast)
    {
        le_um_ase_t *sink_ase = NULL;
        le_um_ase_t *source_ase = NULL;

        LeUnicastManager_GetAsesForGivenSide(Multidevice_GetSide(), &sink_ase, &source_ase);

        if (leUnicastVoiceSource_IsAseReady(sink_ase) && source_ase != NULL && leUnicastVoiceSource_IsAseReady(source_ase))
        {
            audio_available = TRUE;
        }
    }

    return audio_available;
}

static bool leUnicastVoiceSource_IsAudioRouted(voice_source_t source)
{
    if (source == voice_source_le_audio_unicast)
    {
        if (LeUnicastManager_IsContextTypeConversational(LeUnicastManager_GetContext()->audio_context) &&
            LeUnicastManager_IsAnyAseEnabled())
        {
            return TRUE;
        }
    }

    return FALSE;
}

const voice_source_audio_interface_t * LeUnicastVoiceSource_GetAudioInterface(void)
{
    return &voice_source_audio_interface;
}

void LeUnicastVoiceSource_Init(void)
{
    /* Register with audio source for Audio use case */
    VoiceSources_RegisterAudioInterface(voice_source_le_audio_unicast,
                                        LeUnicastVoiceSource_GetAudioInterface());

    VoiceSources_RegisterTelephonyControlInterface(voice_source_le_audio_unicast,
                                                   LeVoiceSource_GetTelephonyControlInterface());
}

#endif /* defined(INCLUDE_LE_AUDIO_UNICAST) */
